#ifndef  HELPER_UTIL_NOTIFICATIONCATEGORY_H
#define  HELPER_UTIL_NOTIFICATIONCATEGORY_H

#include <unordered_map>
#include <list>
#include <functional>

//TODO: how to handle nullptr when attach(observer);
namespace helper {
namespace util {

template<typename O, typename K>
class INotificationCategory {
public:
    virtual ~INotificationCategory(){}

    virtual bool attach(O * observer, const K & key) = 0;
    virtual bool detach(O * observer, const K & key) = 0;
    virtual int visit(const std::function<int(O * )> & cb, const K & key) = 0;

    virtual size_t size(const K & key) const = 0;
};


template<typename O>
class ExactlyNotification: public INotificationCategory<O, unsigned long>{
public:
    typedef std::unordered_map<unsigned long, O *> ObserversMap;

    ~ExactlyNotification() override {}
    bool attach(O * observer, const unsigned long & key = 0) override{
        unsigned long k = reinterpret_cast<unsigned long>(observer);
        auto iter = map.find(k);
        if(iter == map.end()){
            map.insert(std::make_pair(k, observer));
            return true;
        }
        return false;
    }
    bool detach(O * observer, const unsigned long & key = 0) override {
        unsigned long k = reinterpret_cast<unsigned long>(observer);
        auto iter = map.find(k); 
        if(iter != map.end()){
            map.erase(iter);
            return true;
        }
        return false;
    }
    int visit(const std::function<int(O *)> & cb, const unsigned long & key = 0) override{
        for(auto & p : map){
            if(p.second != nullptr){
                if(0 != cb(p.second)){
                    return -1;
                }
            }
        }
        return 0;
    }
    size_t size(const unsigned long & key = 0) const override {
        return map.size();
    }
private:
    ObserversMap map;
};
        
template<typename O, typename AspectType = unsigned long>
class AspectNotification : public INotificationCategory<O, AspectType> {
public:
    typedef std::unordered_map<AspectType, std::list<O*>> ObserversMap;

    ~AspectNotification() override{}

    bool attach(O * observer, const AspectType & key) override{
        auto iter = map.find(key);
        if(iter == map.end()){
            map.insert(std::pair<AspectType, std::list<O *>>(key, {observer}));
        }
        else{
            iter->second.push_back(observer);
        }
        return true;
    }
    bool detach(O * observer, const AspectType &key) override{
        auto iter = map.find(key);
        if(iter != map.end()){
            map.erase(iter);
            return true;
        }
        return false;
    }
    int visit(const std::function<int(O *)> & cb, const AspectType & key) override{
        auto iter = map.find(key);
        if(iter != map.end()){
            for(auto & observer : iter->second){
                if(observer != nullptr)
                    if( 0 != cb(observer)){
                        return -1;
                    }
            }
        }
        return 0;
    }
    size_t size(const AspectType & key) const override {
        auto iter = map.find(key);
        if(iter != map.end()){
            return iter->second.size();
        }
        return 0;
    }
private:
    ObserversMap map;
};

} // end of namespace util
} // end of namespace helper

#endif   /* HELPER_UTIL_NOTIFICATIONCATEGORY_H */
